package so.sao.code.service.service.impl;

import static so.sao.code.service.infrastructure.EventsPublisher.publish;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.time.LocalDate;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.multipart.MultipartFile;

import so.sao.code.dto.enums.ThirdPartyCodeFileStatus;
import so.sao.code.dto.request.ThirdPartyCodeCreate;
import so.sao.code.dto.response.ThirdPartyCodeDto;
import so.sao.code.service.config.CloudResourceContainerProperties;
import so.sao.code.service.config.LocalResourceDirectoryProperties;
import so.sao.code.service.domain.FileType;
import so.sao.code.service.domain.ThirdPartyCodeFileInfoProvider;
import so.sao.code.service.domain.UserBaseInfoWrapper;
import so.sao.code.service.domain.entity.CodeSupplierConfig;
import so.sao.code.service.domain.entity.ThirdPartyCodeFileMessage;
import so.sao.code.service.domain.entity.ThirdPartyCodeFileRecord;
import so.sao.code.service.domain.entity.ThirdPartyCodeFileRecord.CodeException;
import so.sao.code.service.domain.event.CodeFileErrorLogEvent;
import so.sao.code.service.domain.event.CodeSegmentErrorEvent;
import so.sao.code.service.infrastructure.model.vo.ThirdPartCodeFileVO;
import so.sao.code.service.infrastructure.stream.ProduceManager;
import so.sao.code.service.repository.ThirdPartyCodeFileRecordDao;
import so.sao.code.service.service.CodeSupplierConfigService;
import so.sao.code.service.service.TagBatchLookupTableService;
import so.sao.code.service.service.ThirdPartyCodeFileHandleService;
import so.sao.code.service.utils.FileUtils;
import so.sao.ffw.dto.message.TagMapperMessage;
import so.sao.ffw.feign.client.CodeFileHandleClient;
import so.sao.file.datastore.exception.FileDatastoreException;
import so.sao.file.datastore.service.FileService;
import so.sao.sso.feign.dto.IsvUserDTO;
import so.sao.top.core.util.exceptions.ServiceException;

/**
 * 抽象处理
 * 
 * @author ww
 *
 */
// TODO 待整理。
public abstract class AbstractCodeFilePackageHandleService implements ThirdPartyCodeFileHandleService {

	private static Logger logger = LoggerFactory.getLogger(AbstractCodeFilePackageHandleService.class);

	@Autowired
	private LocalResourceDirectoryProperties localResourceDirectoryProperties;

	@Autowired
	private CloudResourceContainerProperties cloudResourceContainerProperties;

	@Autowired
	private FileService fileService;

	@Autowired
	private CodeSupplierConfigService codeSupplierConfigService;

	@Autowired
	private TagBatchLookupTableService tagBatchLookupTableService;

	@Autowired
	private ThirdPartyCodeFileRecordDao thirdPartyCodeFileRecordDao;

	@Autowired
	private CodeFileHandleClient codeFileHandleClient;

	public void validate(IsvUserDTO user, MultipartFile file, ThirdPartCodeFileVO rex) {
		// 简单检查
		if (file.isEmpty() || file.getSize() <= 0) {
			throwException(null, "文件不能为空");
		}
		String originalFilename = file.getOriginalFilename();
		if (!originalFilename.endsWith(rex.getFileType())) {
			throwException(null, "文件类型不一致");
		}

		if (rex.getMd5() == null || rex.getMd5().length() == 0) {
			throwException(null, "md5 为空");
		}

		if (rex.getCodeType() == null || rex.getCodeType() > 10 || rex.getCodeType() <= 0) {
			throwException(null, "codeType 非法");
		}

		if (user == null || user.getEseId() == null || user.getEseId().equals(0L)) {
			throwException(null, "非法用户");
		}

		if (user == null || user.getCompanyId() == null || user.getCompanyId().equals(0L)) {
			throwException(null, "Company Id/code 无效");
		}
	}

	@Override
	public UserBaseInfoWrapper wrapUsebaseInfo(IsvUserDTO userDto) {
		logger.debug(this.getClass().getName() + " wrapUsebaseInfo method start");
		CodeSupplierConfig config = codeSupplierConfigService.getByCompanyId(userDto.getCompanyId());
		if (config == null) {
			throwException(null, "企业没有配置");
		}
		UserBaseInfoWrapper baseInfo = new UserBaseInfoWrapper();
		baseInfo.setUser(userDto);
		baseInfo.setConstantConfig(config);
		logger.debug(this.getClass().getName() + " wrapUsebaseInfo method end");
		return baseInfo;
	}

	//保存未删除本地文件。
	@Override
	public ThirdPartyCodeFileInfoProvider saveFile(UserBaseInfoWrapper usebaseInfo,
			ThirdPartyCodeFileInfoProvider wrapper, MultipartFile file) {
		logger.debug(this.getClass().getName() + " saveFile method start");
		// 检查文件名是否存在
		String fileName = file.getOriginalFilename();
		
		fileName = getName(fileName);
		if(StringUtils.isBlank(fileName)){
			throwException(null, "文件名不存在或不符合要求。");
		}
		
		ThirdPartyCodeFileRecord handleInfo = thirdPartyCodeFileRecordDao
				.findByCompanyIdAndFileName(usebaseInfo.getCompanyId(), fileName);
		if (handleInfo != null) {
			throwException(null, "文件已经存在");
		}

		// 下载
		String localPath = null;
		try {
			// TODO 如果本地存在。删除/覆盖。
			localPath = FileUtils.writeFileToDisk(localResourceDirectoryProperties.getPackagePath(), file);
			wrapper.setLocalPath(localPath);
		} catch (Exception e1) {
			throwException(e1, "文件本地保存失败");
		}

		try {
			// 文件类型检验。
			if (wrapper.getFileType() == FileType.ZIP && !validateZipFile(localPath)) {
				throwException(null, "文件类型不为zip");
			}

			// MD5 校验。
			FileInputStream fileInputStream1 = null;
			try {
				fileInputStream1 = new FileInputStream(localPath);
				String md5Hex = DigestUtils.md5Hex(fileInputStream1);
				logger.info("localPath-md5: " + md5Hex);
				if (!wrapper.getMd5().equals(md5Hex)) {
					throwException(null, "md5 验证失败。");
				}
			} catch (FileNotFoundException e1) {
				throwException(e1, "系统错误");
			} catch (IOException e1) {
				throwException(e1, "系统错误");
			} finally {
				if (fileInputStream1 != null) {
					try {
						fileInputStream1.close();
					} catch (IOException e) {
						logger.error("fileInputStream1 closed exception", e);
					}
				}
			}

			// 上传
			FileInputStream fileInputStream = null;
			String cloudFilePath = generateCloudPath(usebaseInfo, fileName);
			logger.info(cloudFilePath);
			try {
				fileInputStream = new FileInputStream(localPath);
				fileService.upload(cloudResourceContainerProperties.getThirdParty(), fileInputStream, cloudFilePath,
						wrapper.getFileType().getName());
			} catch (IOException e) {
				throwException(e, "上传云端错误");
			} catch (FileDatastoreException e) {
				throwException(e, "上传云端错误");
			} finally {
				if (fileInputStream != null) {
					try {
						fileInputStream.close();
					} catch (IOException e) {
						logger.error("fileInputStream closed exception", e);
					}
				}
			}

			// 保存记录
			ThirdPartyCodeFileRecord entity;
			try {
				entity = new ThirdPartyCodeFileRecord();
				entity.setEseId(usebaseInfo.getEseId());
				entity.setCompanyId(usebaseInfo.getCompanyId());
				entity.setCsId(usebaseInfo.getUserId());
				entity.setContainerName(cloudResourceContainerProperties.getThirdParty());
				entity.setFileName(fileName);
				entity.setFilePath(cloudFilePath);
				entity.setFileType(wrapper.getFileType());
				entity.setCodeType(wrapper.getCodeType());
				entity.setParsePassWord(wrapper.getParsePassWord());
				entity.setPackageCount(wrapper.getPackageCount());
				entity.setCapCount(wrapper.getCapCount());
				entity.setStatus(ThirdPartyCodeFileStatus.INIT_FILE);
				entity.setTerminal(usebaseInfo.getTerminal());
				ThirdPartyCodeFileRecord saveEntity = thirdPartyCodeFileRecordDao.save(entity);
				wrapper.setRecord(saveEntity);
			} catch (Exception e) {
				throwException(e, "数据库保存失败");
			}
		} catch (Exception e) {
			// 出现异常删除zip文件。
			deleteFile(localPath);
			throw e;
		}
		logger.debug(this.getClass().getName() + " saveFile method end");
		return wrapper;
	}

	/**
	 * 记录并抛异常
	 * 
	 * @param e
	 * @param errorInfo
	 */
	void throwException(Throwable e, String errorInfo) {
		logger.error(errorInfo, e);
		if (e == null) {
			throw new ServiceException(errorInfo);
		}
		throw new ServiceException(errorInfo, e);
	}

	/**
	 * 生成云端filePath
	 * 
	 * @param usebaseInfo
	 * @param fileInfo
	 * @param fileName
	 * @return
	 */
	String generateCloudPath(UserBaseInfoWrapper usebaseInfo, String fileName) {
		LocalDate today = LocalDate.now();
		if (StringUtils.isNotEmpty(usebaseInfo.getCloudPathKey())) {
			return today.getYear()+File.separator+usebaseInfo.getCompanyId() + File.separator + usebaseInfo.getCloudPathKey() + File.separator
					+ fileName;
		}
		return today.getYear()+File.separator+usebaseInfo.getCompanyId() + File.separator + fileName;
	}

	/**
	 * 
	 * @param user
	 * @param wrapper
	 * @return
	 */
	public boolean mapping(UserBaseInfoWrapper user, ThirdPartyCodeFileInfoProvider wrapper) {
		try {
			if (wrapper.getCapCount() == null || wrapper.getPackageCount() == null
					|| wrapper.getPackageCount().intValue() == 0 || wrapper.getCapCount().intValue() == 0) {
				throwException(null, "瓶码数或箱码数不存在");
			}
			// 获取top码段。
			ThirdPartyCodeCreate create = new ThirdPartyCodeCreate();
			create.setTagCount(Long.valueOf(wrapper.getCapCount() + wrapper.getPackageCount()));

			ThirdPartyCodeDto createCodeSegment = null;
			try {
				createCodeSegment = tagBatchLookupTableService.createCodeSegment(create, user.getCompanyId());
			} catch (ServiceException e) {
				// 需要指明是具体的exception.
				CodeSegmentErrorEvent event = new CodeSegmentErrorEvent();
				event.setCsId(user.getUserId());
				event.setEseId(user.getCompanyId());
				event.setDescription(e.getMessage());
				publish(event);
				throwException(e, "第三方码段创建失败");
				return Boolean.FALSE;
			} catch (Exception e) {
				throwException(e, "第三方码段创建失败");
				return Boolean.FALSE;
			}

			// dao记录
			ThirdPartyCodeFileRecord handleInfo = thirdPartyCodeFileRecordDao
					.findByCompanyIdAndFileName(user.getCompanyId(), wrapper.getFileName());
			if (handleInfo == null) {
				throwException(null, "第三方码文件不存在");
			}
			handleInfo.setSeqNumStart(createCodeSegment.getSeqnumStart());
			handleInfo.setSeqNumEnd(createCodeSegment.getSeqnumEnd());
			handleInfo.setTagBatchId(createCodeSegment.getTagBatchId());
			handleInfo.setDomain(createCodeSegment.getDomain());
			handleInfo.setSubDomain(createCodeSegment.getSubDomain());
			handleInfo.setStatus(ThirdPartyCodeFileStatus.MAPPING_OK);
			wrapper.setRecord(thirdPartyCodeFileRecordDao.save(handleInfo));
			return Boolean.TRUE;
		} catch (Exception e) {
			dealError(e, null, user, wrapper, ThirdPartyCodeFileStatus.MAPPING_NG);
			throw e;
		}
	}

	/**
	 * sendFFW
	 * 
	 * @param user
	 * @param wrapper
	 */
	public void sendFFW(UserBaseInfoWrapper user, ThirdPartyCodeFileInfoProvider wrapper) {
		try {
			// 发送数据给ffw
			boolean pushSuccess = pushFFw(user, wrapper);

			// 记录状态。
			ThirdPartyCodeFileRecord record = thirdPartyCodeFileRecordDao
					.findByCompanyIdAndFileName(user.getCompanyId(), wrapper.getFileName());
			if (pushSuccess) {
				record.setStatus(ThirdPartyCodeFileStatus.SEND_OK);
				record.setMessageId(wrapper.getMessageId());
			} else {
				throwException(null, "发送ffw失败");
			}
			wrapper.setRecord(thirdPartyCodeFileRecordDao.save(record));
		} catch (Exception e) {
			dealError(e, null, user, wrapper, ThirdPartyCodeFileStatus.SEND_NG);
			throw e;
		}
	}

	/**
	 * 删除本地文件。
	 * 
	 * @param filePath
	 */
	void deleteFile(String filePath) {
		try {
			File f = new File(filePath);
			if (f.exists()) {
				f.delete();
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	@Autowired
	private ProduceManager produceManager;


	/**
	 * 发送给供应链
	 * 
	 * @param e
	 * @param msg
	 */
	public void pushSignmatrix(UserBaseInfoWrapper user, ThirdPartyCodeFileInfoProvider wrapper) {
		try {
			ThirdPartyCodeFileMessage message = new ThirdPartyCodeFileMessage();
			ThirdPartyCodeFileRecord record = wrapper.getRecord();
			message.setEseId(record.getEseId());
			message.setCompanyId(record.getCompanyId());
			message.setCsId(record.getCsId());
			message.setContainerName(record.getContainerName());
			message.setFilePath(record.getFilePath());
			message.setFileType(record.getFileType().getId());
			message.setCodeType(record.getCodeType().getId());
			message.setParsePassWord(record.getParsePassWord());
			message.setDomain(record.getDomain());
			message.setSubDomain(record.getSubDomain());
			boolean sendOk = produceManager.send(message); //TODO 失败是否尝试。
			// 记录状态。
			record = thirdPartyCodeFileRecordDao
					.findOne(record.getId());
			if (sendOk) {
				record.setStatus(ThirdPartyCodeFileStatus.SEND_SM_OK);
			} else {
				throwException(null, "发送供应链失败");
			}
			wrapper.setRecord(thirdPartyCodeFileRecordDao.save(record));
			
		} catch (Exception e) {
			dealError(e, "pushSignmatrix 失败", user, wrapper, ThirdPartyCodeFileStatus.SEND_SM_NG);
			throw e;
		}
		
		
	}

	/**
	 * pushFFw
	 * 
	 * @param user
	 * @param wrapper
	 * @return
	 */
	boolean pushFFw(UserBaseInfoWrapper user, ThirdPartyCodeFileInfoProvider wrapper) {
		// 填充
		TagMapperMessage message = new TagMapperMessage();
		ThirdPartyCodeFileRecord record = wrapper.getRecord();
		message.setCompanyId(record.getCompanyId());
		message.setEseId(record.getEseId());
		message.setContainerName(record.getContainerName());
		message.setFilePath(record.getFilePath());
		message.setParsePassWord(record.getParsePassWord());
		message.setTagType(record.getCodeType().getId());
		message.setSeqnumStart(record.getSeqNumStart());
		message.setSeqnumEnd(record.getSeqNumEnd());
		String[] split = record.getDomain().split("/");
		message.setDomain(split[split.length-1]);
		message.setSubDomain(record.getSubDomain());
		message.setCodeFileId(record.getId());
		message.setCsId(record.getCsId());
		message.setTerminal(record.getTerminal());
		message.setFileName(record.getFileName());
		String messageId = UUID.randomUUID().toString();
		message.setMessageId(messageId);
		wrapper.setMessageId(messageId);

		try {// 发送
			codeFileHandleClient.tagMapperMessageToQueue(message);
		} catch (Exception e) {
			throwException(e, "发送到ffw失败");
		}
		return true;
	}

	/**
	 * 
	 * @param usebaseInfo
	 * @param wrapper
	 * @return
	 * @throws FileDatastoreException
	 */
	String downLocalFromCloud(ThirdPartyCodeFileInfoProvider wrapper) {
		if (wrapper.getFileName() == null) {
			throwException(null, "getFileName failed");
		}
		String localFilePath = generateLocalFilePath(wrapper.getFileName());
		try {
			fileService.downFileToLocal(wrapper.getContainerName(), wrapper.getFilePath(),
					generateLocalFilePath(wrapper.getFileName()), true);
		} catch (Exception e) {
			Map<String, Object> errorInfo = new HashMap<>();
			errorInfo.put("cloud-containerName", wrapper.getContainerName());
			errorInfo.put("cloud-filePath", wrapper.getFilePath());
			errorInfo.put("local-filePath", localFilePath);
			throwException(e, "downLoad failed. info: " + errorInfo.toString());
		}
		return localFilePath;
	}

	/**
	 * 处理失败
	 * 
	 * @param e
	 * @param msg
	 */
	void dealError(Exception e, String msg, UserBaseInfoWrapper user, ThirdPartyCodeFileInfoProvider wrapper,
			ThirdPartyCodeFileStatus status) {
		// dao记录
		if (wrapper.getRecordId() == null) {
			throwException(null, "非法参数:recordId 不存在");
		}
		ThirdPartyCodeFileRecord handleInfo = thirdPartyCodeFileRecordDao.findOne(wrapper.getRecordId());
		if (handleInfo == null) {
			throwException(null, "第三方码文件不存在");
		}
		handleInfo.setException(CodeException.EXCEPTION.getValue());
		if (status != null) {
			handleInfo.setStatus(status);
		}
		handleInfo.setRemark(msg);
		if (StringUtils.isBlank(msg)) {
			handleInfo.setRemark(e.getMessage());
		} else {
			handleInfo.setRemark(msg);
		}
		thirdPartyCodeFileRecordDao.save(handleInfo);
		sendMail(handleInfo);
	}

	/**
	 * 发邮件
	 * 
	 * @param handleInfo
	 */
	void sendMail(ThirdPartyCodeFileRecord handleInfo) {
		CodeFileErrorLogEvent event = new CodeFileErrorLogEvent();
		event.setErrorMessage(handleInfo.getRemark());
		event.setRemark("status: " + handleInfo.getStatus().getName());
		event.setFileId(handleInfo.getId());
		event.setCompanyId(handleInfo.getCompanyId());
		event.setTopic("第三方码同步");
		publish(event);
	}
	
	/**
	 * 
	 * @param fileName
	 * @return
	 */
	String generateLocalFilePath(String fileName) {
		String absolutePath = localResourceDirectoryProperties.getPackagePath();
		if (!StringUtils.endsWith(absolutePath, File.separator)) {
			absolutePath += File.separator;
		}
		return absolutePath + fileName;
	}

	/**
	 * 校验是否是zip文件。
	 * 
	 * @param filename
	 * @return
	 */
	private boolean validateZipFile(String filename) {
		RandomAccessFile raf;
		try {
			raf = new RandomAccessFile(filename, "r");
			long n = raf.readInt();
			raf.close();
			if (n == 0x504B0304)
				return true;
		} catch (Exception e) {
			e.printStackTrace();
		}
		return false;
	}


	/**
	 * 从文件路径中截取文件名(不包括文件扩展名)
	 * 
	 * @param path
	 * @return
	 */
	protected String getFileNameFromPath(String path) {
		String fileName = new File(path).getName();
		return fileName.substring(0, fileName.lastIndexOf("."));
	}

	/**
	 * 从文件名中获取文件后缀名(包含 . ,纯小写)
	 * 
	 * @param fileName
	 * @return
	 */
	protected String getFileSuffixFromName(String fileName) {
		return fileName.substring(fileName.lastIndexOf(".")).toLowerCase();
	}
	
	
	
	/**
	 * 截取文件名。e.g. xxx\\bbb/cc//dd.txt -> dd.txt
	 * @param path
	 * @return
	 */
    public static String getName(String path) {
        int index = path.lastIndexOf(File.separatorChar);
    	String name1 = path.substring(index + 1); 
        int index2 = name1.lastIndexOf('/');
        return name1.substring(index2 + 1);
    }
}
